package com.liang.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.liang.common.to.mq.SeckillOrderTo;
import com.liang.common.utils.R;
import com.liang.common.vo.MemberVo;
import com.liang.gulimall.seckill.feign.CouponFeignService;
import com.liang.gulimall.seckill.feign.ProductFeignService;
import com.liang.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.liang.gulimall.seckill.service.SecKillService;
import com.liang.gulimall.seckill.to.SecKillSkuInfoRedis;
import com.liang.gulimall.seckill.vo.SeckillSessionVo;
import com.liang.gulimall.seckill.vo.SeckillSkuRelationVo;
import com.liang.gulimall.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SecKillServiceImpl implements SecKillService {
    private final String SESSION_PREFIX = "seckill:session:";
    private final String SECKILL_PREFIX = "seckill:sku";
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public void uploadSecKillSkuWith3Day() {
        //TODO 商品过期时间
        //1、扫描最近3天需要参与秒杀的的活动
        R r = couponFeignService.getLast3DaySession();
        if (r.getCode() == 0) {
            List<SeckillSessionVo> data = r.getData("data", new TypeReference<List<SeckillSessionVo>>() {
            });
            //商品缓存到redis
            //1、缓存活动信息
            //2、缓存商品信息
            if (data != null && data.size() > 0) {
                for (SeckillSessionVo sessionVo : data) {
                    saveSessionInfo(sessionVo);
                    saveSkuInfo(sessionVo);
                }
            }

        } else {
            log.error("远程调用coupon失败");
        }
    }

    @Override
    public List<SecKillSkuInfoRedis> getCurrentSecSku() {
        List<SecKillSkuInfoRedis> list = new ArrayList<>();
        //确定当前时间属于哪个秒杀场次
        long now = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SESSION_PREFIX + "*");
        for (String key : keys) {
            //seckill:session:1614657600000_1614661200000
            String[] split = key.split(":|_");
            long startTime = Long.parseLong(split[2]);
            long endTime = Long.parseLong(split[3]);
            if (now >= startTime && now <= endTime) {
                List<String> sessions = redisTemplate.opsForList().range(key, 0, -1);
                System.out.println(sessions + "session-----------");
                //获取这个场次的商品
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_PREFIX);
                List<String> objects = ops.multiGet(sessions);
                if (objects != null && objects.size() > 0) {
                    for (String object : objects) {
                        SecKillSkuInfoRedis skuInfoRedis = JSONObject.parseObject(object, SecKillSkuInfoRedis.class);
                        list.add(skuInfoRedis);
                    }
                }
            }
        }
        return list;
    }

    @Override
    public SecKillSkuInfoRedis getSkuSecKillInfo(Long skuId) {
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_PREFIX);
        Set<String> keys = ops.keys();
        if (keys != null && keys.size() > 0) {
            /*String regx="\\d_"+skuId;//使用正则表达式匹配
            for (String key : keys) {
                if(Pattern.matches(regx,key)){
                    String skuSeckillInfo = ops.get(key);
                    SecKillSkuInfoRedis skuInfoRedis = JSONObject.parseObject(skuSeckillInfo, SecKillSkuInfoRedis.class);
                    //判断该商品此刻是否正在参与秒杀，不是则不返回随机码
                    long now = new Date().getTime();
                    Long startTime = skuInfoRedis.getStartTime();
                    Long endTime = skuInfoRedis.getEndTime();
                    if (now<startTime || now>endTime){
                        skuInfoRedis.setRandomCode(null);
                    }
                    return skuInfoRedis;
                }
            }*/
            for (String key : keys) {
                //key:  1_13
                String[] s = key.split("_");//拆分
                if (s[1].equals(skuId.toString())) {
                    String skuSeckillInfo = ops.get(key);
                    SecKillSkuInfoRedis skuInfoRedis = JSONObject.parseObject(skuSeckillInfo, SecKillSkuInfoRedis.class);
                    //判断该商品此刻是否正在参与秒杀，不是则不返回随机码
                    long now = new Date().getTime();
                    Long startTime = skuInfoRedis.getStartTime();
                    Long endTime = skuInfoRedis.getEndTime();
                    if (now < startTime || now > endTime) {
                        skuInfoRedis.setRandomCode(null);
                    }
                    return skuInfoRedis;
                }
            }
        }
        return null;
    }

    /**
     * @param killId 格式：sessionID_skuId
     * @param key
     * @param num
     * @return
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberVo memberVo = LoginUserInterceptor.loginUserThread.get();
        //登录拦截器判断登录
        //从redis获取秒杀商品的详细信息吗，合法性校验
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SECKILL_PREFIX);
        String skuRedisInfoJson = ops.get(killId);
        if (StringUtils.isEmpty(skuRedisInfoJson)) {
            return null;
        }
        SecKillSkuInfoRedis skuInfoRedis = JSONObject.parseObject(skuRedisInfoJson, SecKillSkuInfoRedis.class);
        //时间检验
        Long startTime = skuInfoRedis.getStartTime();
        Long endTime = skuInfoRedis.getEndTime();
        long now = new Date().getTime();
        if (now >= startTime && now <= endTime) {
            //合法
            //随机码校验
            String randomCode = skuInfoRedis.getRandomCode();
            Long skuId = skuInfoRedis.getSkuId();
            String sessionSkuKey = skuInfoRedis.getPromotionSessionId() + "_" + skuId;
            int count = skuInfoRedis.getSeckillLimit().intValue();
            if (!randomCode.equals(key) || !sessionSkuKey.equals(killId) || num > count) {
                log.error("随机码和对应关系,商品数量校验失败");
                return null;
            }
            //是否购买过
            String seckillUser = memberVo.getId() + "_" + killId;
            long ttl = endTime - startTime;
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(seckillUser, num.toString(), ttl, TimeUnit.MILLISECONDS);
            if (!aBoolean) {
                log.error("已经购买过");
                return null;
            }

            //信号量,库存
            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + key);
            if (semaphore.tryAcquire(num) ){
                //生成订单号
                String orderSn = IdWorker.getTimeId();
                //TODO  发送给MQ
                SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                seckillOrderTo.setOrderSn(orderSn);
                seckillOrderTo.setMemberId(memberVo.getId());
                seckillOrderTo.setSeckillPrice(skuInfoRedis.getSeckillPrice());
                seckillOrderTo.setNum(num);
                seckillOrderTo.setSkuId(skuInfoRedis.getSkuId());
                seckillOrderTo.setPromotionSessionId(skuInfoRedis.getPromotionSessionId());
                rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);
                return orderSn;

            } else{
                log.info("秒杀商品库存不足");
                return null;
            }

        } else {
            log.error("时间检验错误");
            return null;
        }
    }

    private void saveSessionInfo(SeckillSessionVo sessionVo) {
        long start = sessionVo.getStartTime().getTime();
        long end = sessionVo.getEndTime().getTime();
        String key = SESSION_PREFIX + start + "_" + end;
        if (!redisTemplate.hasKey(key)) { //处理定时任务重复上架
            List<String> skuIds = sessionVo.getSkuRelationEntities().stream().map(item -> {
                return item.getPromotionSessionId() + "_" + item.getSkuId().toString();
            }).collect(Collectors.toList());
            redisTemplate.opsForList().leftPushAll(key, skuIds);
        }

    }

    private void saveSkuInfo(SeckillSessionVo sessionVo) {
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SECKILL_PREFIX);
        List<SeckillSkuRelationVo> skus = sessionVo.getSkuRelationEntities();
        if (skus != null && skus.size() > 0) {
            for (SeckillSkuRelationVo sku : skus) {//处理定时任务重复上架
                if (!hashOps.hasKey(sku.getPromotionSessionId() + "_" + sku.getSkuId().toString())) {
                    SecKillSkuInfoRedis secKillSkuInfoRedis = new SecKillSkuInfoRedis();
                    BeanUtils.copyProperties(sku, secKillSkuInfoRedis);
                    R info = productFeignService.info(sku.getSkuId());
                    if (info.getCode() == 0) {
                        SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                        });
                        //设置商品信息
                        secKillSkuInfoRedis.setSkuInfoVo(skuInfo);
                    } else {
                        log.error("feign远程调用product失败");
                    }
                    secKillSkuInfoRedis.setStartTime(sessionVo.getStartTime().getTime());
                    secKillSkuInfoRedis.setEndTime(sessionVo.getEndTime().getTime());
                    //随机码
                    String code = UUID.randomUUID().toString().replace("-", "");
                    secKillSkuInfoRedis.setRandomCode(code);
                    hashOps.put(sku.getPromotionSessionId() + "_" + sku.getSkuId().toString(), JSONObject.toJSONString(secKillSkuInfoRedis));
                    //引入分布式信号量，每进来信号量减一
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + code);
                    semaphore.trySetPermits(sku.getSeckillCount().intValue());//秒杀数量作为信号量
                }
            }
        }
    }
}
